from django.http import HttpResponse, HttpResponseForbidden, HttpResponseServerError
from django.db.models.loading import get_model
from django.contrib.auth import authenticate
from django.utils import simplejson
from django.db import transaction, models
from django.core import serializers
from django.utils.encoding import force_unicode

import cStringIO as StringIO
import csv, codecs

class UnicodeWriter:
    """
    A CSV writer which will write rows to CSV file "f",
    which is encoded in the given encoding.
    """

    def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
        # Redirect output to a queue
        self.queue = StringIO.StringIO()
        self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
        self.stream = f
        self.encoder = codecs.getincrementalencoder(encoding)()
        self.encoding = encoding

    def writerow(self, row):
        self.writer.writerow([force_unicode(s, self.encoding, errors='ignore').encode(self.encoding) for s in row])
        # Fetch UTF-8 output from the queue ...
        data = self.queue.getvalue()
        data = data.decode(self.encoding)
        # ... and reencode it into the target encoding
        data = self.encoder.encode(data)
        # write to the target stream
        self.stream.write(data)
        # empty queue
        self.queue.truncate(0)

    def writerows(self, rows):
        for row in rows:
            self.writerow(row)

def apply_files(obj, files):
    for key, value in files.items():
        getattr(obj, key).save(value.name, value, False)
    
def get_relation(value, field):
    if isinstance(value, dict):
        args = prep_query(value)
        return field.rel.to.objects.get(**args)
    else:
        return field.rel.to.objects.get(pk=value)

def add_action(model, datum, files, m2m_data):
    if 'pk' in datum['fields']:
        if len(model.objects.filter(pk=datum['fields']['pk'])):
            return {'warning':'object with pk: %s already exists' % datum['fields']['pk']}
    obj = model(**datum['fields'])
    return update_object(obj, datum, files, m2m_data)

def delete_action(model, datum):
    model.objects.filter(**datum['fields']).delete()
    return {'fields':datum['fields']}

def change_action(model, datum, files, m2m_data):
    obj = model.objects.get(pk=datum['fields']['pk'])
    for key, value in datum['fields'].items():
        setattr(obj, key, value)
    return update_object(obj, datum, files, m2m_data)

def update_object(obj, datum, files, m2m_data):
    apply_files(obj, files)
    obj.save()
    for key, value in m2m_data.items():
        for val in value:
            getattr(obj, key).add(val)
    return {'pk':obj.pk}

def prep_fields(fields, model, files):
    ret = dict()
    ret_files = dict()
    m2m_data = dict()
    for key, value in fields.items():
        key = str(key)
        if key == 'pk':
            field = model._meta.pk
        else:
            field = model._meta.get_field(key)
        if field.rel:
            if isinstance(value, list):
                for val in value:
                    m2m_data[key].append(get_relation(val, field))
                continue
            else:
                value = get_relation(value, field)
        elif isinstance(field, models.FileField):
            if value and value in files:
                value = files[value]
                ret_files[key] = value
                continue
            else:
                value = None
        ret[key] = value
    return ret, ret_files, m2m_data

def prep_query(fields):
    ret = dict()
    for key, value in fields.items():
        ret[str(key)] = value
    return ret

def serialize_csv(data):
    response = HttpResponse(mimetype='text/csv')
    data = data[0]
    response['Content-Disposition'] = 'attachment; filename=%s.csv' % data[0]['model']
    writer = UnicodeWriter(response)
    # Write headers to CSV file
    headers = data[0]['fields'].keys()
    writer.writerow(headers)
    # Write data to CSV file
    for obj in data:
        row = obj['fields'].values()
        writer.writerow(row)
    # Return CSV file to browser as download
    return response

def serialize_json(response):
    return HttpResponse(simplejson.dumps(response))

# Create your views here.
REMOTE_ACTIONS = {'add':add_action,
                  'delete':delete_action,
                  'change':change_action,}

FORMAT_SERIALIZERS = {'csv':serialize_csv,
                      'json':serialize_json,}

FORMAT_DESERIALIZERS = {'json':simplejson.loads,}

python_serializer = serializers.get_serializer("python")()

class RemoteException(Exception):
    def __init__(self, response, *args, **kwargs):
        self.response = response
        
def handle_remote_objects(data, files, user):
    for datum in data:
        model = get_model(*datum['model'].split('.'))
        action = datum['action']
        if not action in REMOTE_ACTIONS:
            raise RemoteException(HttpResponseServerError('%s is not a supported action' % action))
        if not user.has_perm('.'.join([model._meta.app_label, getattr(model._meta, 'get_%s_permission' % action)()])):
            raise RemoteException(HttpResponseForbidden('You do not have %s permission to %s' % (action, datum['model'])))
        if 'pk' in datum:
            datum['fields']['pk'] = datum.pop('pk')
        if action == 'delete':
            datum['fields'] = prep_query(datum['fields'])
            cx = REMOTE_ACTIONS[action](model, datum)
        else:
            datum['fields'], dfiles, m2m_data = prep_fields(datum['fields'], model, files)
            cx = REMOTE_ACTIONS[action](model, datum, dfiles, m2m_data)
        cx.update({'model':datum['model'],
                   'action':action})
        yield cx

def handle_query(query_list, user):
    for datum in query_list:
        model = get_model(*datum['model'].split('.'))
        if 'query' in datum:
            queryset = model.objects.filter(prep_query(datum['query']))
        else:
            queryset = model.objects.all()
        data = python_serializer.serialize(queryset)
        files = list()
        for field in model._meta.fields:
            if isinstance(field, models.FileField):
                files.append(field.name)
        
        if files:
            index = 0
            for object in queryset:
                for name in files:
                    try:
                        data[index]['fields'][name] = getattr(object, name).url
                    except ValueError:
                        pass
                index =+ 1
        yield data

def authorize(func):
    def wrapper(request, *args, **kwargs):
        if not request.user.is_authenticated():
            if 'username' not in request.REQUEST or 'password' not in request.REQUEST:
                return HttpResponseForbidden('You must login or supply valid credentials')
            user = authenticate(username=request.REQUEST['username'], password=request.REQUEST['password'])
        else:
            user = request.user
        if not user:
            return HttpResponseForbidden('The credentials you supplied were not valid')
        if not user.is_active:
            return HttpResponseForbidden('Your user account is disabled')
        return func(request, user, *args, **kwargs)
    return wrapper

def query_and_response(func):
    @transaction.commit_manually
    def wrapper(request, *args, **kwargs):
        format = None
        if 'format' in request.REQUEST:
            format = request.REQUEST['format']
        else:
            for key in FORMAT_DESERIALIZERS.keys():
                if key in request.REQUEST:
                    format = key
                    break
        if ((format is None) or (format not in FORMAT_DESERIALIZERS)):
            return HttpResponseServerError('You must supply a following format: %s' % str(FORMAT_DESERIALIZERS.keys()))
        data = request.REQUEST[format]
        if 'export_format' in request.REQUEST:
            export_format = request.REQUEST['export_format']
        else:
            export_format = format
        if export_format not in FORMAT_SERIALIZERS:
            return HttpResponseServerError('Unsupported export format %s' % export_format)
        data = FORMAT_DESERIALIZERS[format](data)
        try:
            ret = func(request, data, *args, **kwargs)
            ret = FORMAT_SERIALIZERS[export_format](ret)
        except RemoteException, e:
            transaction.rollback()
            return e.response
        #except Exception, e:
        #    transaction.rollback()
        #    raise e
        transaction.commit()
        return ret
    return wrapper
        
@authorize
@query_and_response
def remote_objects(request, data, user):
    return list(handle_remote_objects(data, request.FILES, user))

@authorize
@query_and_response
def view_objects(request, data, user):
    return list(handle_query(data, user))
